package org.tau.workshop2011.codegen

import scala.collection.mutable.{ Queue => MutableQueue, HashMap => MutableHashMap }
import org.tau.workshop2011.expressions.Type

object Property {
  /* Old value, New value */
  type ChangeListener[T, R] = (Property[T], T, T) => R
}

trait Property[T] {
  def get: T
  def set(value: T)

  /* Change listeners will be called only once the property was already
   * changed. */
  def addChangeListener[R](listener: Property.ChangeListener[T,R])
}

class UpdateManager {
  type Command = () => _

  val props = new MutableQueue[DSLProperty[_]]
  val updateCommands = new MutableHashMap[DSLProperty[_], Command]
  val dependencies = new MutableHashMap[DSLProperty[_], Set[DSLProperty[_]]]

  var lockCount = 0

  def lockUpdates() {
    lockCount += 1
  }

  def addUpdateCommand[T](prop: DSLProperty[T], command: Command) {
    assert (props contains prop)
    assert(!(updateCommands contains prop))
    updateCommands += prop -> command
  }

  def unlockUpdates() {
    var updated:Set[DSLProperty[_]] = Set()

    assert(lockCount >= 0)

    lockCount -= 1

    if (lockCount == 0) {
      for (prop <- props) {
        if (updateCommands contains prop) {
          prop.prepareChangeTacking()
          updateCommands(prop)()
          updateCommands.remove(prop)
          updated ++= Set(prop)
          prop.notifyOnChange()
        } else if (! (dependencies(prop) intersect updated).isEmpty) {
          prop.prepareChangeTacking()
          prop.reComputeAndSet()
          updated ++= Set(prop)
          prop.notifyOnChange()
        }
      }
    }
  }

  def registerProperty[T](prop: DSLProperty[T], dependencies: Set[DSLProperty[_]]) {
    assert (!(props contains prop))
    props enqueue prop
    this.dependencies += prop -> dependencies
  }
}

abstract class DSLProperty[T](val pType: Type, um: UpdateManager, dependencies: DSLProperty[_]*) extends Property[T] {
  um.registerProperty(this, dependencies.toSet)

  private var ChangeListeners: List[Property.ChangeListener[T,_]] = Nil
  protected var oldCache: T = get

  /* Returns the value of the property */
  override def get: T

  /* If this property depends on other values, then this should compute
   * the value it should get right now. Otherwise, this should just get
   * the current value (same as get) */
  def reCompute(): T

  /**
   * Change the value of the property right now and without any
   * conditions.
   */
  def realSet(value: T)

  /**
   * Generates all the commands necessary to update this property to a
   * given value
   */
  def requestUpdate(newValue: T)

  /**
   * Is this property a constant?
   */
  def isReadOnly: Boolean

  def set(value: T) {
    if (isReadOnly)
      throw new UnsupportedOperationException()

    um.lockUpdates()
//    um.addUpdateCommand(this, () => realSet(value))
    requestUpdate(value)
    um.unlockUpdates()
  }

  def addChangeListener[R](listener: Property.ChangeListener[T,R]) {
    ChangeListeners ::= listener
  }

  def reComputeAndSet() {
    realSet(reCompute())
  }

  def prepareChangeTacking() {
    oldCache = get
  }

  def notifyOnChange() {
    for (listener <- ChangeListeners)
      listener(this, oldCache, get)
  }
}

class InitialProperty[T](initialValue: T, um: UpdateManager) extends DSLProperty[T](Type fromValue initialValue, um) {
  protected var currentValue: T = initialValue

  def get: T = currentValue

  def reCompute(): T = currentValue

  def realSet(value: T) {currentValue = value}

  def requestUpdate(newValue: T) {
    um.addUpdateCommand(this, () => realSet(newValue))
  }

  def isReadOnly: Boolean = false
}

abstract class DefinedProperty[T](pType: Type, um: UpdateManager, dependencies: DSLProperty[_]*) extends DSLProperty[T](pType, um, dependencies: _*) {
  protected var cache: T = reCompute()

  def reCompute(): T

  def requestUpdate(newValue: T)

  def isReadOnly: Boolean

  def get: T = cache

  def realSet(value: T) {cache = value}
}